/*
 * =====================================================================================
 *
 *       Filename:  catch-entityconverter.cpp
 *
 *    Description:  Unit tests for EntityConverter class
 *
 *        Version:  1.0
 *        Created:  07/14/2017 20:36:31 PM
 *       Revision:  none
 *       Compiler:  gcc
 *
 *         Author:  surkeh@protonmail.com
 *
 * =====================================================================================
 */
#include <cmath>
#include <queue>
#include <vector>
#include <sstream>

#include "EntityConverter.hpp"

#define ENTITY_FILENAME "../r2xonotic.rem"
#define DELTA 0.00001



TEST_CASE ("r2x: Unsupported entity types cause return of empty vector", "[EntityConverter]") {
  
  // Instantiate object
  EntityConverter ec (ENTITY_FILENAME);
  
  // Mock up entity
  std::vector<std::string> entity;
  entity.push_back ("		type NotAType");

  // Mock up entity queue
  std::queue<std::vector<std::string>> q;
  q.push (entity);

  // Match related entities (none)
  ec.extractMapInfo (q);
  
  // Convert a single entity
  std::vector<std::string> converted = ec.convert (entity);

  REQUIRE (converted.size() == 0);
}



TEST_CASE ("r2x: a single Pickup entity can be converted", "[EntityConverter]") {
  
  // Instantiate object
  EntityConverter ec (ENTITY_FILENAME);
  
  // Mock up entity
  std::vector<std::string> entity;
  entity.push_back ("		type Pickup");
  entity.push_back ("		Vector3 position -216.00000 -132.00000 -1488.000488");
  entity.push_back ("		Vector3 angles 180.00000 0.00000 0.00000");
  entity.push_back ("		UInt8 pickupType 2");

  // Mock up entity queue
  std::queue<std::vector<std::string>> q;
  q.push (entity);

  // Match related entities (none)
  ec.extractMapInfo (q);
  
  // Convert a single entity
  std::vector<std::string> converted = ec.convert (entity);

  REQUIRE (converted[0] == "\"classname\" \"weapon_grenadelauncher\"\n");

  // The z (vertical) is offset by +2
  std::istringstream iss (converted[1]);
  std::string attribute;
  std::string coords[2];
  float offsetCoord;
  iss >> attribute >> coords[0] >> coords[1] >> offsetCoord;

  REQUIRE (attribute == "\"origin\"");
  REQUIRE (coords[0] == "\"-216.00000");
  REQUIRE (coords[1] == "-1488.000488");
  REQUIRE (fabs(-130.00000 - offsetCoord) <= DELTA);
}



TEST_CASE ("r2x: a single PlayerSpawn (race) entity can be converted", "[EntityConverter]") {
  
  // Instantiate object
  EntityConverter ec (ENTITY_FILENAME);
  
  // Mock up WorldSpawn entity
  // (needed for mode info)
  std::vector<std::string> worldspawn;
  worldspawn.push_back ("		type WorldSpawn");
  worldspawn.push_back ("		Bool8 modeCTF 0");
  worldspawn.push_back ("		Bool8 modeFFA 0");
  worldspawn.push_back ("		Bool8 modeTDM 0");
  worldspawn.push_back ("		Bool8 mode1v1 0");


  // Mock up entity
  std::vector<std::string> entity;
  entity.push_back ("		type PlayerSpawn");
  entity.push_back ("		Vector3 position -216.00000 -132.00000 -1488.000488");
  entity.push_back ("		Vector3 angles 180.00000 0.00000 0.00000");
  entity.push_back ("		Bool8 teamA 0");
  entity.push_back ("		Bool8 teamB 0");
  entity.push_back ("		Bool8 modeCTF 0");
  entity.push_back ("		Bool8 modeFFA 0");
  entity.push_back ("		Bool8 modeTDM 0");
  entity.push_back ("		Bool8 mode1v1 0");

  // Mock up entity queue
  std::queue<std::vector<std::string>> q;
  q.push (worldspawn);
  q.push (entity);

  // Match related entities (none)
  ec.extractMapInfo (q);
  
  // Convert a single entity (worldspawn conversion returns empty vector
  // BUT sets the supported game modes for entities in that worldspawn
  std::vector<std::string> unused = ec.convert(worldspawn);
  std::vector<std::string> converted = ec.convert(entity);

  REQUIRE (converted[0] == "\"classname\" \"info_player_start\"\n");
  
  // The z (vertical) is offset by +32
  std::istringstream iss (converted[1]);
  std::string attribute;
  std::string coords[2];
  float offsetCoord;
  iss >> attribute >> coords[0] >> coords[1] >> offsetCoord;

  REQUIRE (attribute == "\"origin\"");
  REQUIRE (coords[0] == "\"-216.00000");
  REQUIRE (coords[1] == "-1488.000488");
  REQUIRE (fabs(-100.00000 - offsetCoord) <= DELTA);

  SECTION( "Converted angles are valid (Different coordinate system handedness)") {
    std::istringstream angleLineStream(converted[2]);
  std::string angleAttribute;
  std::string a;
  float angle;
  angleLineStream >> attribute >> a;
  a.erase(a.begin()); //removing preceding quote is necessary

  std::stringstream angleStream(a);
  angleStream >> angle;

  REQUIRE (attribute == "\"angle\"");
  REQUIRE (fabs (-90.0 - angle) <= DELTA);
  }


  SECTION( "Encountering a new worldspawn reenables all modes") {
    std::vector<std::string> basicWorldspawn;
    basicWorldspawn.push_back ("		type WorldSpawn");

    std::vector<std::string> e;
    e.push_back ("		type PlayerSpawn");
    e.push_back ("		Vector3 position -216.00000 -132.00000 -1488.000488");
    e.push_back ("		Vector3 angles 180.00000 0.00000 0.00000");

    std::vector<std::string> u = ec.convert(basicWorldspawn);
    std::vector<std::string> c = ec.convert(e);
    REQUIRE (c[0] == "\"classname\" \"info_player_deathmatch\"\n");
  }
}




TEST_CASE ("r2x: a single PlayerSpawn (teamA) entity can be converted", "[EntityConverter]") {
  
  // Instantiate object
  EntityConverter ec (ENTITY_FILENAME);
  
  // Mock up entity
  std::vector<std::string> entity;
  entity.push_back ("		type PlayerSpawn");
  entity.push_back ("		Vector3 position -216.00000 -132.00000 -1488.000488");
  entity.push_back ("		Vector3 angles 180.00000 0.00000 0.00000");
  entity.push_back ("		Bool8 teamB 0");
  entity.push_back ("		Bool8 modeRace 0");

  // Mock up entity queue
  std::queue<std::vector<std::string>> q;
  q.push (entity);

  // Match related entities (none)
  ec.extractMapInfo (q);
  
  // Convert a single entity
  std::vector<std::string> converted = ec.convert(entity);

  REQUIRE (converted[0] == "\"classname\" \"info_player_team1\"\n");
  
  // The z (vertical) is offset by +32
  std::istringstream iss (converted[1]);
  std::string attribute;
  std::string coords[2];
  float offsetCoord;
  iss >> attribute >> coords[0] >> coords[1] >> offsetCoord;

  REQUIRE (attribute == "\"origin\"");
  REQUIRE (coords[0] == "\"-216.00000");
  REQUIRE (coords[1] == "-1488.000488");
  REQUIRE (fabs(-100.00000 - offsetCoord) <= DELTA);
}



TEST_CASE ("r2x: a single PlayerSpawn (non-team) entity can be converted", "[EntityConverter]") {
  
  // Instantiate object
  EntityConverter ec (ENTITY_FILENAME);
  
  // Mock up entity
  std::vector<std::string> entity;
  entity.push_back ("		type PlayerSpawn");
  entity.push_back ("		Vector3 position -216.00000 -132.00000 -1488.000488");
  entity.push_back ("		Vector3 angles 180.00000 0.00000 0.00000");

  // Mock up entity queue
  std::queue<std::vector<std::string>> q;
  q.push (entity);

  // Match related entities (none)
  ec.extractMapInfo (q);
  
  // Convert a single entity
  std::vector<std::string> converted = ec.convert(entity);

  REQUIRE (converted[0] == "\"classname\" \"info_player_deathmatch\"\n");
  
  // The z (vertical) is offset by +32
  std::istringstream iss (converted[1]);
  std::string attribute;
  std::string coords[2];
  float offsetCoord;
  iss >> attribute >> coords[0] >> coords[1] >> offsetCoord;

  REQUIRE (attribute == "\"origin\"");
  REQUIRE (coords[0] == "\"-216.00000");
  REQUIRE (coords[1] == "-1488.000488");
  REQUIRE (fabs(-100.00000 - offsetCoord) <= DELTA);
}




TEST_CASE ("r2x: a single RaceStart entity can be converted", "[EntityConverter]") {
  
  // Instantiate object
  EntityConverter ec (ENTITY_FILENAME);
  
  // Mock up entity
  std::vector<std::string> entity;
  entity.push_back ("		type RaceStart");

  // Mock up entity queue
  std::queue<std::vector<std::string>> q;
  q.push (entity);

  // Match related entities (none)
  ec.extractMapInfo (q);
  
  // Convert a single entity
  std::vector<std::string> converted = ec.convert(entity);

  REQUIRE (converted[0] == "\"classname\" \"trigger_race_checkpoint\"\n");
  REQUIRE (converted[1] == "\"targetname\" \"cp1\"\n");
  REQUIRE (converted[2] == "\"cnt\" \"0\"\n");
}



TEST_CASE ("r2x: a single RaceFinish entity can be converted", "[EntityConverter]") {
  
  // Instantiate object
  EntityConverter ec (ENTITY_FILENAME);
  
  // Mock up entity
  std::vector<std::string> entity;
  entity.push_back ("		type RaceFinish");

  // Mock up entity queue
  std::queue<std::vector<std::string>> q;
  q.push (entity);

  // Match related entities (none)
  ec.extractMapInfo (q);
  
  // Convert a single entity
  std::vector<std::string> converted = ec.convert(entity);

  REQUIRE (converted[0] == "\"classname\" \"trigger_race_checkpoint\"\n");
  REQUIRE (converted[1] == "\"targetname\" \"finish\"\n");
  REQUIRE (converted[2] == "\"cnt\" \"1\"\n");
}



TEST_CASE ("r2x: a single Teleporter and related Target can be converted", "[EntityConverter]") {
  
  // Instantiate object
  EntityConverter ec (ENTITY_FILENAME);
  
  // Mock up Teleporter entity
  std::vector<std::string> entity;
  entity.push_back ("		type Teleporter");
  entity.push_back ("		String32 target tp1");

  // Mock up Target entity
  std::vector<std::string> entity2;
  entity2.push_back ("		type Target");
  entity2.push_back ("		Vector3 position -216.00000 -132.00000 -1488.000488");
  entity2.push_back ("		String32 name tp1");

  // Mock up entity queue
  std::queue<std::vector<std::string>> q;
  q.push (entity);
  q.push (entity2);

  // Match related entities (one pair)
  ec.extractMapInfo (q);
  
  // Convert two entities
  std::vector<std::string> converted = ec.convert(entity);
  REQUIRE (converted[0] == "\"classname\" \"trigger_teleport\"\n");
  REQUIRE (converted[1] == "\"target\" \"tp1\"\n");

  std::vector<std::string> converted2 = ec.convert(entity2);
  REQUIRE (converted2[0] == "\"classname\" \"misc_teleporter_dest\"\n");
  REQUIRE (converted2[2] == "\"targetname\" \"tp1\"\n");
  //
  // The z (vertical) is offset by +32
  std::istringstream iss (converted2[1]);
  std::string attribute;
  std::string coords[2];
  float offsetCoord;
  iss >> attribute >> coords[0] >> coords[1] >> offsetCoord;

  // next REQUIRE fails without busy wait
  for( int i = 0; i < 10000000; i++)
    int x = i;

  REQUIRE (attribute == "\"origin\""); 
  REQUIRE (coords[0] == "\"-216.00000");
  REQUIRE (coords[1] == "-1488.000488");
  REQUIRE (fabs(-100.00000 - offsetCoord) <= DELTA);

  SECTION( "When angle unspecified, defaults to 0.0 (converted to 90.0)") {
    std::istringstream angleStream(converted2[3]);
    std::string a;
    float angle;
    angleStream >> attribute >> a;
    a.erase(a.begin()); //removing preceding quote is necessary
    std::stringstream out(a);
    out >> angle;
    REQUIRE (fabs(90.0 - angle)  <= DELTA);
  }
}



TEST_CASE ("r2x: a single JumpPad and related Target can be converted", "[EntityConverter]") {
  
  // Instantiate object
  EntityConverter ec (ENTITY_FILENAME);
  
  // Mock up JumpPad entity
  std::vector<std::string> entity;
  entity.push_back ("		type JumpPad");
  entity.push_back ("		String32 target jp1");

  // Mock up Target entity
  std::vector<std::string> entity2;
  entity2.push_back ("		type Target");
  entity2.push_back ("		Vector3 position -216.00000 -132.00000 -1488.000488");
  entity2.push_back ("		String32 name jp1");

  // Mock up entity queue
  std::queue<std::vector<std::string>> q;
  q.push (entity);
  q.push (entity2);

  // Match related entities (one pair)
  ec.extractMapInfo (q);
  
  // Convert two entities
  std::vector<std::string> converted = ec.convert(entity);
  REQUIRE (converted[0] == "\"classname\" \"trigger_push\"\n");
  REQUIRE (converted[1] == "\"target\" \"jp1\"\n");

  std::vector<std::string> converted2 = ec.convert(entity2);
  REQUIRE (converted2[0] == "\"classname\" \"target_position\"\n");
  REQUIRE (converted2[1] == "\"origin\" \"-216.00000 -1488.000488 -132.00000\"\n");
  REQUIRE (converted2[2] == "\"targetname\" \"jp1\"\n");
}



TEST_CASE ("r2x: a single PointLight entity can be converted", "[EntityConverter]") {
  
  // Instantiate object
  EntityConverter ec (ENTITY_FILENAME);
  
  // Mock up entity
  std::vector<std::string> entity;
  entity.push_back ("		type PointLight");
  entity.push_back ("		Vector3 position -216.00000 -132.00000 -1488.000488");
  entity.push_back ("		ColourXRGB32 color ffffc400");
  entity.push_back ("		Float intensity 1.500000");
  entity.push_back ("		Float nearAttenuation 32.000000");
  entity.push_back ("		Float farAttenuation 160.000000");

  // Mock up entity queue
  std::queue<std::vector<std::string>> q;
  q.push (entity);

  // Match related entities (none)
  ec.extractMapInfo (q);
  
  // Convert a single entity
  std::vector<std::string> converted = ec.convert(entity);

  REQUIRE (converted[0] == "\"classname\" \"light\"\n");
  REQUIRE (converted[1] == "\"origin\" \"-216.00000 -1488.000488 -132.00000\"\n");
  REQUIRE (converted[2] == "\"light\" \"75\"\n");

    INFO (converted[3]);
    std::istringstream iss (converted[3]);
    std::string attribute;
    std::string r;
    float red;
    float green;
    float blue;
    iss >> attribute >> r >> green >> blue;
    r.erase(r.begin()); //removing preceding quote is necessary
    std::stringstream redStream(r);
    redStream >> red;

    REQUIRE (attribute == "\"_color\"");

    REQUIRE (fabs (1.0 - red)        <= DELTA);
    REQUIRE (fabs (0.768627 - green) <= DELTA);
    REQUIRE (fabs (0.0 - blue)       <= DELTA);
}


TEST_CASE ("r2x: PointLight defaults to white light of typical intensity", "[EntityConverter]") {
 
   // Instantiate object
  EntityConverter ec (ENTITY_FILENAME);
  
  // Mock up entity
  std::vector<std::string> entity;
  entity.push_back ("		type PointLight");
  entity.push_back ("		Vector3 position -216.00000 -132.00000 -1488.000488");

  // Mock up entity queue
  std::queue<std::vector<std::string>> q;
  q.push (entity);

  // Match related entities (none)
  ec.extractMapInfo (q);
  
  // Convert a single entity
  std::vector<std::string> converted = ec.convert(entity);

  REQUIRE (converted[0] == "\"classname\" \"light\"\n");
  REQUIRE (converted[1] == "\"origin\" \"-216.00000 -1488.000488 -132.00000\"\n");
  REQUIRE (converted[2] == "\"light\" \"50\"\n");

  INFO (converted[3]);
  std::istringstream iss (converted[3]);
  std::string attribute;
  std::string r;
  float red;
  float green;
  float blue;
  iss >> attribute >> r >> green >> blue;
  r.erase (r.begin()); //removing preceding quote is necessary
  std::stringstream redStream (r);
  redStream >> red;

  REQUIRE (attribute == "\"_color\"");

  REQUIRE (fabs (0.0 - red)   <= DELTA);
  REQUIRE (fabs (0.0 - green) <= DELTA);
  REQUIRE (fabs (0.0 - blue)  <= DELTA);

}







